MUI v5とReact Hook Form v7でサクッとフォームバリデーションを作る
Material-UI v4とReact Fook Form v6を使い続け、幾多の時が過ぎていた浦島太郎状態の片岡です。
今回は新しいプロジェクトを作る際に、MUI v5(v5でMaterial UIからMUIに名称変更)とReact Hook Form v7を採用した結果今までの苦労はなんだったのか・・・となったのでブログにしました!
準備
MUIとReact Hook Formを使ってフォームを作れるようにしていきます。
Reactプロジェクトを作成
適当にプロジェクトを作ります。
yarn create react-app sample-project --template typescript
ライブラリをインストール
実際に使うライブラリをインストールしていきます。
MUIをインストール
公式に習い下記をインストールします。
yarn add @mui/material @emotion/react @emotion/styled
React Hook Formをインストール
ひとまずReact Hook Formもインストールしておきます。
yarn add react-hook-form
サクッとindex.tsxを直す
CSSをリセットしてくれるCssBaseLineを追加します。
import { CssBaseline } from '@mui/material' import React from 'react' import ReactDOM from 'react-dom' import App from './App' ReactDOM.render( <React.StrictMode> <CssBaseline /> <App /> </React.StrictMode>, document.getElementById('root') )
フォームの作成
MUIを使って適当にフォームを作ります。
import { Button, Container, Stack, TextField } from '@mui/material' import React from 'react' function App() { return ( <Container maxWidth="sm" sx={{ pt: 5 }}> <Stack spacing={3}> <TextField required label="メールアドレス" type="email" /> <TextField required label="お名前" /> <TextField required label="パスワード" type="password" /> <Button color="primary" variant="contained" size="large"> 作成 </Button> </Stack> </Container> ) } export default App
するとこのようなフォームができました。
React Hook Formを接続する
接続するだけならこれだけでできちゃうんですよね・・・すごいですよね・・・
const { register } = useForm() return <TextField {...register("name")} />
そして先程作ったフォームにReact Hook Formを接続するとこうなります。
import { Button, Container, Stack, TextField } from '@mui/material' import React from 'react' import { SubmitHandler, useForm } from 'react-hook-form' // フォームの型 interface SampleFormInput { email: string name: string password: string } function App() { const { register, handleSubmit } = useForm<SampleFormInput>() // フォーム送信時の処理 const onSubmit: SubmitHandler<SampleFormInput> = (data) => { // バリデーションチェックOK!なときに行う処理を追加 console.log(data) } return ( <Container maxWidth="sm" sx={{ pt: 5 }}> <Stack spacing={3}> <TextField required label="メールアドレス" type="email" {...register('email')} /> <TextField required label="お名前" {...register('name')} /> <TextField required label="パスワード" type="password" {...register('password')} /> <Button color="primary" variant="contained" size="large" onClick={handleSubmit(onSubmit)} > 作成 </Button> </Stack> </Container> ) } export default App
バリデーションルールを追加する
バリデーションルール作るの面倒なので下記をインストールします。
yupはスキーマベースのバリデーションを行うライブラリで、@hookform/resolversはyupでバリデーションを行うためにインストールします。
yarn add @hookform/resolvers yup
スキーマを作成
このような感じでスキーマを作成しました。
メールアドレス:必須とメールアドレスとして正しいか
お名前:必須
パスワード:必須・最小6文字・英字と数字と記号が最低1文字必要
メソッドチェーンでルールを追加できるのと、メールアドレスのチェックのために正規表現いちいち書かなくていいのが楽で好きです!
import * as yup from 'yup' // バリデーションルール const schema = yup.object({ email: yup .string() .required('必須だよ') .email('正しいメールアドレス入力してね'), name: yup.string().required('必須だよ'), password: yup .string() .required('必須だよ') .min(6, '少ないよ') .matches( /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&].*$/, 'パスワード弱いよ' ), })
React Hook Formに適用
先程作ったschemaの内容をReact Hook Formのバリデーションルールとして使用します。
import { yupResolver } from '@hookform/resolvers/yup' const { register, handleSubmit } = useForm<SampleFormInput>({ // 追加 resolver: yupResolver(schema), })
全体的にこうなりました。
import { yupResolver } from '@hookform/resolvers/yup' import { Button, Container, Stack, TextField } from '@mui/material' import React from 'react' import { SubmitHandler, useForm } from 'react-hook-form' import * as yup from 'yup' // フォームの型 interface SampleFormInput { email: string name: string password: string } // バリデーションルール const schema = yup.object({ email: yup .string() .required('必須だよ') .email('正しいメールアドレス入力してね'), name: yup.string().required('必須だよ'), password: yup .string() .required('必須だよ') .min(6, '少ないよ') .matches( /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&].*$/, 'パスワード弱いよ' ), }) function App() { const { register, handleSubmit } = useForm<SampleFormInput>({ resolver: yupResolver(schema), }) // フォーム送信時の処理 const onSubmit: SubmitHandler<SampleFormInput> = (data) => { // バリデーションチェックOK!なときに行う処理を追加 console.log(data) } return ( <Container maxWidth="sm" sx={{ pt: 5 }}> <Stack spacing={3}> <TextField required label="メールアドレス" type="email" {...register('email')} /> <TextField required label="お名前" {...register('name')} /> <TextField required label="パスワード" type="password" {...register('password')} /> <Button color="primary" variant="contained" size="large" onClick={handleSubmit(onSubmit)} > 作成 </Button> </Stack> </Container> ) } export default App
エラー内容を表示させる
先程作ったバリデーションルールでSubmitできないことは確認できます。しかしエラー内容が表示されないので今度はエラー内容を表示していきましょう。
エラー内容を拾ってTextFieldに表示するサンプルです。
const { register, formState: { errors } } = useForm() return ( <TextField {...register("name")} // ↓↓追加↓↓ error={"name" in errors} helperText={errors.name?.message} /> )
全体的にこうなりました。
import { yupResolver } from '@hookform/resolvers/yup' import { Button, Container, Stack, TextField } from '@mui/material' import React from 'react' import { SubmitHandler, useForm } from 'react-hook-form' import * as yup from 'yup' // フォームの型 interface SampleFormInput { email: string name: string password: string } // バリデーションルール const schema = yup.object({ email: yup .string() .required('必須だよ') .email('正しいメールアドレス入力してね'), name: yup.string().required('必須だよ'), password: yup .string() .required('必須だよ') .min(6, '少ないよ') .matches( /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&].*$/, 'パスワード弱いよ' ), }) function App() { const { register, handleSubmit, formState: { errors }, } = useForm<SampleFormInput>({ resolver: yupResolver(schema), }) // フォーム送信時の処理 const onSubmit: SubmitHandler<SampleFormInput> = (data) => { // バリデーションチェックOK!なときに行う処理を追加 console.log(data) } return ( <Container maxWidth="sm" sx={{ pt: 5 }}> <Stack spacing={3}> <TextField required label="メールアドレス" type="email" {...register('email')} error={'email' in errors} helperText={errors.email?.message} /> <TextField required label="お名前" {...register('name')} error={'name' in errors} helperText={errors.name?.message} /> <TextField required label="パスワード" type="password" {...register('password')} error={'password' in errors} helperText={errors.password?.message} /> <Button color="primary" variant="contained" size="large" onClick={handleSubmit(onSubmit)} > 作成 </Button> </Stack> </Container> ) } export default App
するとこのようにバリデーションエラーが表示されるようになります。